package cn.sylinx.horm.spring.scan;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;

import cn.sylinx.horm.exception.HORMException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;

import cn.sylinx.horm.proxy.ProxyObjectRegistry;
import cn.sylinx.horm.util.GLog;
import org.springframework.lang.NonNull;

public class ClassPathCommandScanner extends ClassPathBeanDefinitionScanner {

    private Class<? extends Annotation> annotationClass;
    private CommandFactoryBean<?> commandFactoryBean = new CommandFactoryBean<Object>();

    public ClassPathCommandScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }

    public void setCommandFactoryBean(CommandFactoryBean<?> commandFactoryBean) {
        this.commandFactoryBean = commandFactoryBean;
    }

    public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
        this.annotationClass = annotationClass;
    }

    @Override
    public @NonNull Set<BeanDefinitionHolder> doScan(@NonNull String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            GLog.warn("No horm command was found in '" + Arrays.toString(basePackages)
                    + "' package.");
        } else {
            processBeanDefinitions(beanDefinitions);
        }
        return beanDefinitions;
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    @Override
    protected boolean checkCandidate(@NonNull String beanName, @NonNull BeanDefinition beanDefinition) {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        } else {
            GLog.warn(
                    "Skipping CommandFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName()
                            + "' commandInterface" + ". Bean already defined with the same name!");
            return false;
        }
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String className = definition.getBeanClassName();
            if(className == null) {
                throw new HORMException("commandInterface className must not be null");
            }

            GLog.debug("Creating CommandFactoryBean with name '" + holder.getBeanName() + "' and '"
                    + className + "' commandInterface");
            definition.getConstructorArgumentValues().addGenericArgumentValue(className);
            definition.setBeanClass(this.commandFactoryBean.getClass());
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            //CommandRegistry.INSTANCE.addCommond(className);
            ProxyObjectRegistry.INSTANCE.register(className);
        }
    }

    public void registerFilters() {
        boolean acceptAllInterfaces = true;

        if (this.annotationClass != null) {
            addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
            acceptAllInterfaces = false;
        }

        if (acceptAllInterfaces) {
            addIncludeFilter(new TypeFilter() {
                @Override
                public boolean match(@NonNull MetadataReader metadataReader, @NonNull MetadataReaderFactory metadataReaderFactory)
                        throws IOException {
                    return true;
                }
            });
        }

        addExcludeFilter(new TypeFilter() {
            @Override
            public boolean match(@NonNull MetadataReader metadataReader, @NonNull MetadataReaderFactory metadataReaderFactory)
                    throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info");
            }
        });
    }
}